/**
 * \file Process.cc
 * \brief Process facilities
 * \author Corey Tabaka
 */

/*
   Copyright 2006 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <Process.h>
#include <HandleMap.h>
#include <Module.h>
#include <Log.h>
#include <platform/Interrupt.h>
#include <platform/Memory.h>
#include <syscall.h>
#include <string.h>
#include <lzss.h>
#include <pecoff.h>
#include <new.h>

#include <stdio.h>

DEF_NULL_REF(Process);

/*
 * Kernel process
 */

static RefProcess kernelProcess;

/**
 * Next process id
 */
static pid_t nextPid;

/**
 * Returns the next process id in sequence that isn't already in the process
 * list
 */
static pid_t getNextPid(void);

/**
 * Process id to process reference hashtable
 */
static HandleMap<RefProcess> processList;
static InterruptLock processLock;

/**
 * List of modules
 */
static Queue moduleList;
static InterruptLock moduleLock;

void Process::initialize(void) {
	Interrupt::registerHandler(INT_SYSCALL, syscallHandler);
	
	kernelProcess = RefProcess(new Process("System",
		Memory::getPhysicalPageMap()));
}

RefProcess Process::getSystemProcess(void) {
	return kernelProcess;
}

RefProcess Process::getProcess(pid_t pid) {
	RefProcess proc = nullProcess;
	processList.get(pid, proc);
	
	return proc;
}

RefProcess Process::createProcess(const char *name) {
	Process *proc = new Process(name);
	return proc->self;
}

RefProcess Process::getCurrentProcess(void) {
	return Thread::getCurrentThread()->getProcess();
}

Process::Process(const char *name) : self(this, true), threads(16),
	pipeCount(0), pipes(8), lock(), asLock() {
	pid = getNextPid();
	strcpy(this->name, name ? name : "Unnamed");
	pageMap = new PageMap();
	
	// setup the initial page table for referencing the system logical addresses
	pageTable.acquire(0, 0, MEM_IDENTITY_MAP_SIZE, false);
	pageTable.acquire(0, 0, PAGE_SIZE, false, ACQUIRE_MODE_REPLACE_PTE
		| ACQUIRE_MODE_READ_ONLY);
	
	processList.put(pid, self);
	
	// use placement new to initialize the address space
	new (&addressSpace) AddressSpace(&pageTable, pageMap);
}

Process::Process(const char *name, PageMap &pageMap) : self(this, true),
	threads(16), pipeCount(), pipes(8), lock(), asLock() {
	pid = getNextPid();
	strcpy(this->name, name ? name : "Unnamed");
	this->pageMap = &pageMap;
	
	// setup the initial page table for referencing the system logical addresses
	pageTable.acquire(0, 0, MEM_IDENTITY_MAP_SIZE, false);
	pageTable.acquire(0, 0, PAGE_SIZE, false, ACQUIRE_MODE_REPLACE_PTE
		| ACQUIRE_MODE_READ_ONLY);
	
	processList.put(pid, self);
	
	// use placement new to initialize the address space
	new (&addressSpace) AddressSpace(&pageTable, &pageMap);
}

Process::~Process(void) {
	processList.remove(pid);
}

PageTable &Process::getPageTable(void) {
	return pageTable;
}

PageMap &Process::getPageMap(void) {
	return *pageMap;
}

AddressSpace &Process::getAddressSpace(void) {
	return addressSpace;
}

const char *Process::getName(void) {
	return name;
}

handle_t Process::createPipe(void) {
	lock.lock();
	
	handle_t handle = ++pipeCount;
	while (pipes.containsHandle(pipeCount)) {
		handle = ++pipeCount;
	}
	
	RefMessagePipe mp = RefMessagePipe(new MessagePipe());
	pipes.put(handle, mp);
	
	lock.unlock();
	
	return handle;
}

void Process::destroyPipe(handle_t handle) {
	lock.lock();
	
	pipes.remove(handle);
	
	lock.unlock();
}

RefMessagePipe Process::getPipe(handle_t handle) {
	lock.lock();
	
	RefMessagePipe mp = RefMessagePipe(0);
	pipes.get(handle, mp);
	
	lock.unlock();
	
	return mp;
}

static pid_t getNextPid(void) {
	while (processList.containsHandle(nextPid)) {
		nextPid++;
	}
	
	return nextPid;
}

bool Process::loadBinary(const void *image, uint32 length, uint32 at,
	bool compressed) {
	lock.lock();
	
	// find an area that spans AT through AT+LENGTH
	Region *region = addressSpace.findType(REGION_TYPE_VM86);
	
	if (!region) {
		region = addressSpace.create(0, 0, (1024 + 64) * 1024,
			REGION_FLAGS_VM86, REGION_TYPE_VM86);
	}
	
	uint8 *base = (uint8 *) region->getPhysicalBase()
		+ (at - region->getVirtualBase());
	
	if (compressed) {
		decompress_lzss(base, (uint8 *) image, length);
	} else {
		memcpy(base, image, length);
	}
	
	lock.unlock();
	
	return true;
}

bool Process::loadPeExe(const void *image, uint32 length, bool compressed) {
	uint8 *buffer;
	
	if (compressed) {
		buffer = new uint8[length * 4];
		decompress_lzss(buffer, (uint8 *) image, length);
	} else {
		buffer = (uint8 *) image;
	}
	
	Region *region = addressSpace.findType(REGION_TYPE_VGA);
	if (!region) {
		region = addressSpace.create(0, 0, 0x100000,/*0xb8000, 0xb8000, 0x48000,*/
			REGION_FLAGS_CONTIG | REGION_FLAGS_USER | REGION_FLAGS_WIRED,
			REGION_TYPE_VGA);
	}
	
	bool ret;
	uint32 entry;
	if (::loadPeExe(buffer, addressSpace, (uint32 *) &entry) == 0) {
		Log::printf(LOG_LOW, "Successfully loaded PE-COFF into process\n");
		
		RefThread thread = UserThread::createThread((ThreadEntryPoint) entry,
			0, "main", self);
		thread->run();
		
		ret = true;
	} else {
		Log::printf(LOG_MEDIUM, "Failed to load PE-COFF\n");
		
		ret = false;
	}
	
	if (compressed) {
		delete buffer;
	}
	
	return ret;
}

bool Process::loadPeModule(const void *image, uint32 length, bool compressed) {
	uint8 *buffer;
	
	if (compressed) {
		buffer = new uint8[length * 4];
		decompress_lzss(buffer, (uint8 *) image, length);
	} else {
		buffer = new uint8[length];
		memcpy(buffer, image, length);
	}
	
	bool ret;
	Module *module=0;
	
	if (loadPeCoffRelocatable(buffer, &module) == 0) {
		//Log::printf(LOG_LOW, "Successfully loaded PE-COFF module into process"
		//	" at %08x\n", (uint32) where);
		
		Log::printf(LOG_LOW, "Loaded PE-COFF module '%s'\n", module->getName());
		
		moduleLock.lock();
		moduleList.append(module);
		moduleLock.unlock();
		
		// call the module's init
		module->callInit();
		
		ret = true;
	} else {
		Log::printf(LOG_MEDIUM, "Failed to load PE-COFF module\n");
		
		ret = false;
	}
	
	delete buffer;
	
	return ret;
}

#define _CLASS (syscallParam(regs, 0))
#define _FUNC (syscallParam(regs, 1))
#define _ARG(n) (syscallParam(regs, (n)+2))

bool Process::syscallHandler(regs_t *regs) {
	bool handled = false;
	
	if (_CLASS == SYSCALL_MESSAGE) {
		switch(_FUNC) {
			case SYSCALL_MESSAGE_PIPE_CREATE: {
				RefProcess proc = Process::getCurrentProcess();
				regs->eax = proc->createPipe();
				
				//printf("Created pipe %d\n", regs->eax);
			}
			break;
			
			case SYSCALL_MESSAGE_PIPE_DESTROY: {
				RefProcess proc = Process::getCurrentProcess();
				proc->destroyPipe(_ARG(0));
				
				//printf("Destroyed pipe %d\n", _ARG(0));
			}
			break;
			
			case SYSCALL_MESSAGE_SEND: {
				handle_t pid = _ARG(0);
				handle_t qid = _ARG(1);
				
				RefMessagePipe pipe(0);
				
				processLock.lock();
				RefProcess proc(0);
				processList.get(pid, proc);
				if (proc) {
					pipe = proc->getPipe(qid);
				}
				processLock.unlock();
				
				if (pipe) {
					//printf("IPC send: sbuf=%08x slen=%d rbuf=%08x rlen=%d\n",
					//	_ARG(2), _ARG(3), _ARG(4), _ARG(5));
					regs->eax = pipe->send((void *) _ARG(2), _ARG(3),
						(void *) _ARG(4), _ARG(5));
				} else {
					Log::printf(LOG_MEDIUM, "Failed to find message queue qid="
						"%d\n", qid);
					regs->eax = (uint32) -1;
				}
			}
			break;
			
			case SYSCALL_MESSAGE_RECEIVE: {
				handle_t qid = _ARG(0);
				RefMessagePipe pipe(0);
				
				RefProcess proc = Process::getCurrentProcess();
				if (proc) {
					pipe = proc->getPipe(qid);
				}
				
				if (pipe) {
					//printf("IPC receive: rbuf=%08x rlen=%d\n", _ARG(1),
					//	_ARG(2));
					regs->eax = pipe->receive((void *) _ARG(1), _ARG(2));
				} else {
					Log::printf(LOG_MEDIUM, "Failed to find message queue qid="
						"%d\n", qid);
					regs->eax = (uint32) -1;
				}
			}
			break;
			
			case SYSCALL_MESSAGE_REPLY: {
				handle_t qid = _ARG(0);
				RefMessagePipe pipe(0);
				
				RefProcess proc = Process::getCurrentProcess();
				if (proc) {
					pipe = proc->getPipe(qid);
				}
				
				if (pipe) {
					//printf("IPC reply: rbuf=%08x rlen=%d\n", _ARG(1), _ARG(2));
					regs->eax = pipe->reply((void *) _ARG(1), _ARG(2));
				} else {
					Log::printf(LOG_MEDIUM, "Failed to find message queue qid="
						"%d\n", qid);
					regs->eax = (uint32) -1;
				}
			}
			break;
			
			default:
				Log::printf(LOG_MEDIUM, "Unrecognized SYSCALL_MESSAGE function "
					"%u\n", _FUNC);
				break;
		}
		
		handled = true;
	}
	
	return handled;
}
